The C# Station Tutorial
by Joe Mayo, 10/12/02, updated 3/12/03, 2/22/08, 4/29/08, 1/12/09
Lesson 15: Introduction to Exception Handling
This lesson teaches how to handle exceptions in your C# programs. Our objectives
are as follows:
- Learn what an exception is
- Implement a routine with a try/catch block
- Release resources in a finally block
Exceptions
Exceptions are unforeseen errors that happen in your programs. Most of the time,
you can, and should, detect and handle program errors in your code. For example,
validating user input, checking for null objects, and verifying the values returned
from methods are what you expect, are all examples of good standard error handling
that you should be doing all the time.
However, there are times when you don't know if an error will occur. For example,
you can't predict when you'll receive a file I/O error, run out of system memory,
or encounter a database error. These things are generally unlikely, but they could
still happen and you want to be able to deal with them when they do occur. This
is where exception handling comes in.
When exceptions occur, they are said to be "thrown". What is actually thrown is
an object that is derived from the System.Exception class. In the next section,
I'll be explaining how thrown exceptions are handled with try/catch blocks.
The System.Exception class provides several methods and properties for obtaining
information on what went wrong. For example, the Message property provides
summary information about what the error was, the Stacktrace property provides
information from the stack for where the problem occurred, and the ToString()
method is overridden to reveal a verbose description of the entire exception.
Identifying the exceptions you'll need to handle depends on the routine you're writing.
For example, if the routine opened a file with the System.IO.File.OpenRead()
method, it could throw any of the following exceptions:
- SecurityException
- ArgumentException
- ArgumentNullException
- PathTooLongException
- DirectoryNotFoundException
- UnauthorizedAccessException
- FileNotFoundException
- NotSupportedException
It's easy to find out what exceptions a method can raise by looking in the .NET
Frameworks SDK Documentation. Just go to the Reference/Class Library section and
look in the Namespace/Class/Method documentation for the methods you use. The exception
in the list above were found by looking at the OpenRead() method definition
of the File class in the System.IO namespace. Each exception identified
has a hyperlink to its class definition that you can use to find out what that exception
is about. Once you've figured out what exceptions can be generated in your code,
you need to put the mechanisms in place to handle the exceptions, should they occur.
try/catch Blocks
When exceptions are thrown, you need to be able to handle them. This is done by
implementing a try/catch block. Code that could throw an exception is put
in the try block an exception handling code goes in the catch block.
Listing 15-1 shows how to implement a try/catch block. Since an OpenRead()
method could throw one of several exceptions, it is placed in the try
block. If an exception is thrown, it will be caught in the catch block. The
code in Listing 15-1 will print message and stack trace information out to the console
if an exception is raised.
Note: The programs in this lesson cause exceptions on purpose. The exception
that you see is generated intentionally to show you what the exception message looks
like before you see it yourself in your own programs.
Listing 15-1. Using try/catch Blocks: tryCatchDemo.cs
using System;
using System.IO;
class tryCatchDemo
{
static
void Main(string[] args)
{
try
{
File.OpenRead("NonExistentFile");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
Although the code in Listing 15-1 only has a single catch block, all exceptions
will be caught there because the type is of the base exception type "Exception".
In exception handling, more specific exceptions will be caught before their more
general parent exceptions. For example, the following snippet shows how to place
multiple catch blocks:
catch(FileNotFoundException fnfex)
{
Console.WriteLine(fnfex.ToString());
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
If the file doesn't exist, a FileNotFoundException exception will be thrown
and caught by the first catch block. However, if a PathTooLongException
exception was raised, the second catch part would catch the exception. This is because
there isn't a catch block for the PathTooLongException exception and
the generic Exception type catch block is the only option available
to catch the exception.
Exceptions that are not handled will normally bubble up the stack until a calling
routine in the call chain handles them. If you forget to include try/catch
blocks in a part of your code and there aren't any try/catch blocks earlier
in the call chain, your program will abort with a message describing the exception.
To your users this would be very cryptic and uncomfortable. It is good practice
to provide exception handling in your programs.
Finally Blocks
An exception can leave your program in an inconsistent state by not releasing resources
or doing some other type of cleanup. A catch block is a good place to figure
out what may have gone wrong and try to recover, however it can't account for all
scenarios. Sometimes you need to perform clean up actions whether or not your program
succeeds. These situations are good candidates for using a finally block.
Listing 15-2 illustrates the usefulness of a finally block. As you know,
a file stream must be closed when you're done with it. In this case, the file stream
is the resource that needs to be cleaned up. In Listing 15-2, outStream is
opened successfully, meaning the program now has a handle to an open file resource.
When trying to open the inStream, a FileNotFoundException exception
is raised, causing control to go immediately to the catch block.
It's possible to close the outStream in the catch block, but what if the
algorithm executed successfully without an exception? On success, the file
would never be closed. Fortunately, we've included a finally block in Listing
15-2, which will always be executed. That's right, regardless of whether the algorithm
in the try block raises an exception or not, the code in the finally
block will be executed before control leaves the method.
Listing 15-2. Implementing a finally Block: FinallyDemo.cs
using System;
using System.IO;
class FinallyDemo
{
static
void Main(string[] args)
{
FileStream outStream =
null;
FileStream inStream = null;
try
{
outStream = File.OpenWrite("DestinationFile.txt");
inStream = File.OpenRead("BogusInputFile.txt");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (outStream != null)
{
outStream.Close();
Console.WriteLine("outStream closed.");
}
if (inStream != null)
{
inStream.Close();
Console.WriteLine("inStream closed.");
}
}
}
}
A finally block is not required and you may ask what happens if you just put code
after the catch block. True, under normal circumstances, if the exception is caught,
all code following the catch will be executed. However, try/catch/finally is for
exceptional circumstances and it is better to plan for the worst to make your program
more robust. For example, if one of the catch handlers rethrew an exception or
caused another exception, the code following the catch block (not in a finally block)
would never be executed. Also, if you don't catch the exception at all, program
flow would immediately do a stack walk looking for an exception handler that fits
and the code following the catch blocks would not be executed. Since there is too
much potential for code in an algorithm to not be executed, a finally block is your
insurance for executing those critical actions you need.
Summary
This has been an introduction to handling exceptions. By now, you should have a
good understanding of what an exception is. You can implement algorithms within
try/catch blocks that handle exceptions. Additionally, you know how to clean
up resources by implementing a finally block whose code is always executed
before leaving a method.
I invite you to return for Lesson 16: Using Attributes.
Your feedback and constructive contributions are welcome. Please feel free
to contact me for feedback or comments you may have about this lesson.
Copyright © 2000-2010 C# Station, All Rights Reserved